--[[
Copyright (c) 2012 Hello!Game

Permission is hereby granted, free of charge, to any person obtaining a copy
of newinst software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:

The above copyright notice and newinst permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 
]]

local random = math.random;
local cos = math.cos;
local pi = math.pi;
local floor = math.floor;
local abs = math.abs;

local Object = getClass("Object");
local Noise = getClass("Object.Noise");

local function Cosine_Interpolate(a, b, x)
	local f = (1.0 - cos(x * pi)) * .5
	return a*(1-f) + b*f
end

local function Cubic_Interpolate(v0, v1, v2, v3,x)
	local P = (v3 - v2) - (v0 - v1)
	local Q = (v0 - v1) - P
	local R = v2 - v0
	local S = v1
	local x2 = x*x;
	local x3 = x2*x;
 
	return P*x3 + Q*x2 + R*x + S
end

local Perlin = class("Perlin", Object,
{
	width = 128,
	height = 128,
	depth = 1,
	
	octaves = 8,
	frequency = 1,
	persistence = .5,
	
	noise_t = nil,
	
	interpolate = 0,
});

function Perlin:initialize(w, h, d)
	self.width = w or Perlin.width;
	self.height = h or Perlin.height;
	self.depth = d or Perlin.depth;
end

--生成噪声
function Perlin:generateNoise(rand)
	rand = rand or random;
	self.noise_t = {};
	for z=0, self.depth do
		self.noise_t[z] = {};
		
		for y=0, self.height do
			self.noise_t[z][y] = {};
			
			for x=0, self.width do
				self.noise_t[z][y][x] = rand()*2-1; --(-1, 1)
			end
		end
	end
end

function Perlin:smoothNoise(x, y, z)
	x = floor(x or 0)%self.width;
	y = floor(y or 0)%self.height;
	z = floor(z or 0)%self.depth;
	
	if self.depth==1 then
		if self.height==1 then
			local noise = self.noise_t[0][0];
			return noise[x]/2 
				+ noise[(x-1)%self.width]/4
				+ noise[(x+1)%self.width]/4;
		else
			local noise = self.noise_t[0];
			local ym = (y-1)%self.height;
			local yp = (y+1)%self.height;
			local xm = (x-1)%self.width;
			local xp = (x+1)%self.width;
			
			return (noise[ym][xm]
					+noise[yp][xm]
					+noise[ym][xp]
					+noise[yp][xp])/16 			--corner
				+ (noise[ym][x]
					+noise[yp][x]
					+noise[y][xm]
					+noise[y][xp])/8			--side
				+ noise[y][x]/4;				--center
		end
	else
		local noise = self.noise_t;
		local ym = (y-1)%self.height;
		local yp = (y+1)%self.height;
		local xm = (x-1)%self.width;
		local xp = (x+1)%self.width;
		local zm = (z-1)%self.depth;
		local zp = (z+1)%self.depth;
		
		return (noise[zp][ym][xm]
				+noise[zp][yp][xm]
				+noise[zp][ym][xp]
				+noise[zp][yp][xp]
				+noise[zm][ym][xm]
				+noise[zm][yp][xm]
				+noise[zm][ym][xp]
				+noise[zm][yp][xp])/32 			--corner
			+ (noise[z][ym][x]
				+noise[z][yp][x]
				+noise[z][y][xm]
				+noise[z][y][xp]
				+noise[zm][y][x]
				+noise[zp][y][y])/12			--side
			+ noise[z][y][x]/8;					--center
	end
end

-- Cosine插值
function Perlin:interpolateCosine(x, y, z)
	local ix = floor(x);
	local fx = x - ix;
	local iy = floor(y);
	local fy = y - iy;
	local iz = floor(z);
	local fz = z - iz;
	
	if self.depth==1 then
		if self.height==1 then
			local v1 = self:smoothNoise(ix);
			local v2 = self:smoothNoise(ix+1);
			return Cosine_Interpolate(v1, v2, fx);
		else
			local v1 = self:smoothNoise(ix, iy);
			local v2 = self:smoothNoise(ix+1, iy);
			local v3 = self:smoothNoise(ix, iy+1);
			local v4 = self:smoothNoise(ix+1,iy+1);
			
			local i1 = Cosine_Interpolate(v1, v2, fx);
			local i2 = Cosine_Interpolate(v3, v4, fx);
			return Cosine_Interpolate(i1, i2, fy);
		end
	else
		local v1 = self:smoothNoise(ix, iy, iz);
		local v2 = self:smoothNoise(ix+1, iy, iz);
		local v3 = self:smoothNoise(ix, iy+1, iz);
		local v4 = self:smoothNoise(ix+1,iy+1, iz);
		local v5 = self:smoothNoise(ix, iy, iz+1);
		local v6 = self:smoothNoise(ix+1, iy, iz+1);
		local v7 = self:smoothNoise(ix, iy+1, iz+1);
		local v8 = self:smoothNoise(ix+1,iy+1, iz+1);
			
		local i1 = Cosine_Interpolate(v1, v2, fx);
		local i2 = Cosine_Interpolate(v3, v4, fx);
		local i3 = Cosine_Interpolate(v5, v6, fx);
		local i4 = Cosine_Interpolate(v7, v8, fx);
		
		local i5 = Cosine_Interpolate(i1, i2, fy);
		local i6 = Cosine_Interpolate(i3, i4, fy);
		return Cosine_Interpolate(i5, i6, fz);
	end
end

-- Cubic插值
function Perlin:interpolateCubic(x, y, z)
	local ix = floor(x);
	local fx = x - ix;
	local iy = floor(y);
	local fy = y - iy;
	local iz = floor(z);
	local fz = z - iz;
	
	if self.depth==1 then
		if self.height==1 then
			local v0 = self:smoothNoise(ix-1);
			local v1 = self:smoothNoise(ix);
			local v2 = self:smoothNoise(ix+1);
			local v3 = self:smoothNoise(ix+2);
			return Cubic_Interpolate(v0, v1, v2, v3, fx);
		else
			local v = {};
			local xv = {};
			for i=0, 3 do
				for j=0, 3 do
					v[j]=self:smoothNoise(ix-1+j, iy-1+i);
				end
				xv[i]=Cubic_Interpolate(v[0],v[1],v[2],v[3], fx);
			end
			return Cubic_Interpolate(xv[0],xv[1],xv[2],xv[3], fy);
		end
	else
		local v = {};
		local xv = {};
		local yv = {};
		for k=0, 3 do
			for i=0, 3 do
				for j=0, 3 do
					v[j]=self:smoothNoise(ix-1+j, iy-1+i, iz-1+k);
				end
				xv[i]=Cubic_Interpolate(v[0],v[1],v[2],v[3],fx);
			end
			yv[k]=Cubic_Interpolate(xv[0],xv[1],xv[2],xv[3],fy);
		end
		return Cubic_Interpolate(yv[0],yv[1],yv[2],yv[3],fz);
	end
end

function Perlin:turbulance(x, y, z)
	x = x or 0;
	y = y or 0;
	z = z or 0;

	local n = 0;
	local freq = self.frequency;
	local ampl = self.persistence;
	local v = 0;
	for i=0, self.octaves-1 do
		if interpolate==1 then
			v=self:interpolateCubic(x*freq, y*freq, z*freq)*ampl;
		else
			v=self:interpolateCosine(x*freq, y*freq, z*freq)*ampl;
		end
		if v<0 then
			v = v/2;
		end
		n = n-math.abs(v);
		freq = 2*freq;
		ampl = ampl*self.persistence;
	end
	return n;
end

function Perlin:noise(x, y, z)
	x = x or 0;
	y = y or 0;
	z = z or 0;

	local n = 0;
	local freq = self.frequency;
	local ampl = self.persistence;
	for i=0, self.octaves-1 do
		if interpolate==1 then
			n=n+self:interpolateCubic(x*freq, y*freq, z*freq)*ampl;
		else
			n=n+self:interpolateCosine(x*freq, y*freq, z*freq)*ampl;
		end
		freq = 2*freq;
		ampl = ampl*self.persistence;
	end
	return n;
end

function Perlin:Generate(noise, width, height, 
					startx, starty, 
					scalex, scaley,
					depth, startz, scalez,
					co)
	width = width or self.width;
	height = height or self.height;
	
	startx = startx or 0;
	starty = starty or 0;
	scalex = scalex or 1;
	scaley = scaley or 1;
	
	depth = depth or self.depth;
	startz = startz or 0;
	scalez = scalez or 1;
	
	co = co or false;
	
	if noise then
		noise:setSize(width, height, depth);
	else
		noise = Noise(width, height, depth);
	end

	for z=0, depth-1 do
		for y=0, height do
			for x=0, width do
				noise.noise[z][y][x] = self:noise((startx+x)*scalex, (starty+y)*scaley, (startz+z)*scalez);
			end
			if co then
				coroutine.yield(noise, y, z);
			end
		end
	end
	
	return noise, height, depth;
end

function Perlin:CreateCo(noise, width, height, 
						startx, starty, 
						scalex, scaley,
						depth, startz, scalez)
	local co = coroutine.create(function()
		return self:Generate(noise, width, height,
					startx, starty,
					scalex, scaley,
					depth, startz, scalez, true);
	end);
	return co;
end

return Perlin;
